// ModelRenderer.java
package OBJ;

import DataStructure.Face;
import DataStructure.Model;
import DataStructure.Normal;
import DataStructure.TextureCoordinate;
import org.joml.Vector3f;
import org.lwjgl.BufferUtils;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import static org.lwjgl.opengl.GL30.*;

//将obj转化为OpenGL的可渲染的格式
public class ModelRenderer {
    private final int vaoId;//顶点数组对象，VAO用于管理顶点属性和缓冲区状态的对象
    private final int vboId;//顶点缓冲对象，VBO用于存储顶点数据
    private final int eboId;//索引缓冲对象，EBO用于存储索引数据

    //构造函数 ,设置属性，绑定缓冲区
    public ModelRenderer(Model model) {
        FloatBuffer vertexBuffer = convertModelToFloatBuffer(model); // 将模型顶点数据转换为OpenGL可用的FloatBuffer（包含顶点坐标+纹理坐标+法线）
        IntBuffer indexBuffer = convertFacesToIndexBuffer(model);    // 将模型面数据转换为索引缓冲数据（用于顶点复用）
        // 创建OpenGL对象
        vaoId = glGenVertexArrays();
        vboId = glGenBuffers();
        eboId = glGenBuffers();

        // 绑定缓冲区
        glBindVertexArray(vaoId);

        // 顶点数据
        glBindBuffer(GL_ARRAY_BUFFER, vboId);
        glBufferData(GL_ARRAY_BUFFER, vertexBuffer, GL_STATIC_DRAW);

        // 索引数据
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboId);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexBuffer, GL_STATIC_DRAW);

        // 设置属性指针
        // 位置属性 (0)
        glVertexAttribPointer(0, 3, GL_FLOAT, false, 8 * Float.BYTES, 0);
        glEnableVertexAttribArray(0);
        // 纹理坐标属性 (1)
        glVertexAttribPointer(1, 2, GL_FLOAT, false, 8 * Float.BYTES, 3 * Float.BYTES);
        glEnableVertexAttribArray(1);
        // 法线属性 (2)
        glVertexAttribPointer(2, 3, GL_FLOAT, false, 8 * Float.BYTES, 5 * Float.BYTES);
        glEnableVertexAttribArray(2);

        // 解绑
        glBindVertexArray(0);
    }

    //渲染
    public void render(Model model, ModelShaderProgram shader) {
        glBindVertexArray(vaoId);//绑定VAO
        for (Model.Material material : model.materials) {
            // 设置材质属性到着色器
            shader.use();
            shader.setUniform("diffuseColor", material.diffuseColor);
            shader.setUniform("useTexture", material.textureID != -1 ? 1 : 0);

            // 如果有贴图
            if (material.textureID != -1) {
                glActiveTexture(GL_TEXTURE0); // 激活纹理单元0
                glBindTexture(GL_TEXTURE_2D, material.textureID);
                shader.setUniform("diffuseTexture", 0); // 对应纹理单元0
            }
            // 绘制该材质对应的面
            glDrawElements(GL_TRIANGLES, material.faceCount * 3, GL_UNSIGNED_INT, material.startIndex * Integer.BYTES); // 更安全的偏移计算
        }
        glBindVertexArray(0);//解绑VAO
    }


    private FloatBuffer convertModelToFloatBuffer(Model model) {
        // 计算总顶点数
        int totalVertices = 0;
        for (Model.Material material : model.materials) {
            totalVertices += material.faces.size() * 3; // 每个面3个顶点
        }
        // 创建足够容纳所有顶点数据的FloatBuffer，每个顶点包含：位置(3) + 纹理坐标(2) + 法线(3) = 8个float
        FloatBuffer buffer = BufferUtils.createFloatBuffer(totalVertices * 8);
        // 遍历所有材质组（OBJ文件中的usemtl分组）
        for (Model.Material material : model.materials) {
            for (Face face : material.faces) {
                // 获取顶点索引、纹理坐标索引、法线索引
                int[] vIndices = face.getVertexIndices();
                int[] tIndices = face.getTextureCoordinateIndices();
                int[] nIndices = face.getNormalIndices();
                // 遍历面中的每个顶点索引
                for (int i = 0; i < vIndices.length; i++) {
                    // 处理顶点位置（处理可能缺失的情况）
                    int vIndex = vIndices[i];
                    Vector3f vertex = model.getVertices().get(vIndex);

                    // 处理纹理坐标（处理可能缺失的情况）
                    int tIndex = (tIndices != null && i < tIndices.length) ? tIndices[i] : -1;
                    TextureCoordinate texCoord = (tIndex != -1 && tIndex < model.getTextureCoordinates().size())
                            ? model.getTextureCoordinates().get(tIndex)
                            : new TextureCoordinate(0, 0);

                    // 处理法线（处理可能缺失的情况）
                    int nIndex = (nIndices != null && i < nIndices.length) ? nIndices[i] : -1;
                    Normal normal = (nIndex != -1 && nIndex < model.getNormals().size())
                            ? model.getNormals().get(nIndex)
                            : new Normal(0, 0, 0);

                    // 写入缓冲区，位置(x,y,z) + 纹理(u,v) + 法线(i,j,k)
                    buffer.put(vertex.x).put(vertex.y).put(vertex.z);
                    buffer.put(texCoord.getU()).put(texCoord.getV());
                    buffer.put(normal.getI()).put(normal.getJ()).put(normal.getK());
                }
            }
        }
        buffer.flip(); // 切换为读模式
        return buffer;
    }

    private IntBuffer convertFacesToIndexBuffer(Model model) {
        // 生成线性递增索引（假设所有顶点数据都已展开）
        int totalIndices = model.getFaces().size() * 3;
        IntBuffer buffer = BufferUtils.createIntBuffer(totalIndices);

        int currentIndex = 0;
        // 遍历材质组并记录每个材质的索引范围
        for (Model.Material material : model.materials) {
            material.startIndex = currentIndex;         // 记录该材质的起始索引
            int faceCount = material.faces.size();      // 获取当前材质的三角形面数量
            material.faceCount = faceCount;             // 存储面数用于后续渲染调用

            for (int i = 0; i < faceCount * 3; i++) {
                buffer.put(currentIndex++);             // 每个面顶点顺序生成新索引
            }
        }
        buffer.flip();
        return buffer;
    }
}